/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.rafkind.beanxmldeserializer;

import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import nu.xom.Attribute;
import nu.xom.Builder;
import nu.xom.Document;
import nu.xom.Element;
import nu.xom.Elements;
import nu.xom.ParsingException;
import org.apache.commons.beanutils.PropertyUtils;

/**
 *
 * @author drafkind
 */
public class BeanXMLDeserializer {
  public static <T> T loadFrom(InputStream is, Class<T> rootClass, Map<String, Class<?>> classMap)
          throws InstantiationException,
            IllegalAccessException,
            ParsingException,
            IOException,
            InvocationTargetException,
            NoSuchMethodException {

    T currentInstance = rootClass.newInstance();
    
    Builder xmlBuilder = new Builder();
    Document xmlDocument = xmlBuilder.build(is);
    
    treewalk(currentInstance, xmlDocument.getRootElement(), classMap);

    return currentInstance;
  }


  private static <T> T constructFromStringIfPossible(
          Class<T> clazz,
          String string)
          throws InstantiationException,
            IllegalAccessException,
            InvocationTargetException {
    for (Constructor c : clazz.getConstructors()) {
      Class[] pt = c.getParameterTypes();
      if (pt.length == 1 && pt[0].equals(String.class)) {
        return (T) c.newInstance(string);
      }
    }
    return null;
  }

  private static Object treewalk(Object o, 
          Element e,
          Map<String, Class<?>> classMap) throws
          IllegalAccessException,
          InvocationTargetException,
          NoSuchMethodException,
          InstantiationException {
    for (int i = 0; i < e.getAttributeCount(); i++) {
      Attribute att = e.getAttribute(i);
      PropertyDescriptor descriptor =
        PropertyUtils.getPropertyDescriptor(o, att.getLocalName());
      if (descriptor != null) {
        if (descriptor.getPropertyType().isPrimitive()) {
          Class c = (Class) descriptor.getPropertyType();
          if (double.class.equals(c)) {
            PropertyUtils.setSimpleProperty(o, att.getLocalName(), Double.parseDouble(att.getValue()));
          } else if (int.class.equals(c)) {
            PropertyUtils.setSimpleProperty(o, att.getLocalName(), Integer.parseInt(att.getValue()));
          } else if (boolean.class.equals(c)) {
            PropertyUtils.setSimpleProperty(o, att.getLocalName(), Boolean.parseBoolean(att.getValue()));
          }
        } else {
          Object b = constructFromStringIfPossible(descriptor.getPropertyType(), att.getValue());
          PropertyUtils.setSimpleProperty(o, att.getLocalName(), (b == null ? att.getValue() : b));
        }
      }
    }

    Elements children = e.getChildElements();
    for (int i = 0; i < children.size(); i++) {
      Element child = children.get(i);

      PropertyDescriptor descriptor =
        PropertyUtils.getPropertyDescriptor(o, child.getLocalName());

      if (descriptor != null) {

        if (descriptor.getPropertyType().equals(List.class)) {
          Elements childrenOfList = child.getChildElements();
          List list = (List) PropertyUtils.getSimpleProperty(o, child.getLocalName());

          for (int j = 0; j < childrenOfList.size(); j++) {
            Element child2 = childrenOfList.get(j);
            Object next = classMap.get(child2.getLocalName()).newInstance();
            treewalk(next, child2, classMap);
            list.add(next);
          }
        } else {
          Object next = descriptor.getPropertyType().newInstance();
          treewalk(next, child, classMap);
          PropertyUtils.setSimpleProperty(o, child.getLocalName(), next);
        }
      }
    }
    
    return o;
  }
}
